package dispatcher.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.io.IOException;
import java.net.InetAddress;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.RepaintManager;
import javax.swing.ScrollPaneLayout;

import state.Globals;
import system.Constant;

import dispatcher.group.gui.elevator.ElevatorPanel;
import dispatcher.group.gui.elevator.ElevatorPanelList;
import dispatcher.group.gui.floor.FloorPanel;
import dispatcher.group.gui.floor.FloorTitlePanel;
import dispatcher.group.gui.listener.ElevatorInitializeListener;
import dispatcher.group.gui.listener.ElevatorKillListener;
import dispatcher.group.gui.listener.ElevatorUpdateListener;
import dispatcher.group.gui.listener.FloorInitializeListener;
import dispatcher.group.gui.network.GroupDispatcherGUIObserverListener;
import dispatcher.group.gui.network.GroupDispatcherGUIObserverThread;
import dispatcher.gui.event.PersonStatusUpdateThreadPool;
import dispatcher.gui.event.StatusUpdateThreadPool;
import dispatcher.gui.menu.GroupDispatcherViewNetworkMenuItemListener;
import dispatcher.gui.state.GroupDispatcherStateHandler;
import dispatcher.person.gui.PersonDispatcherView;

/**
 * This class is the main GUI for the GroupDispatcher and will also hold the GUI for the PersonDispatcher 
 * in another tab. There is only one instance of this GUI while the system is running.
 * 
 * @author Caleb Chan
 * 
 */
public class GroupDispatcherView {
	
	private static final GroupDispatcherView instance = new GroupDispatcherView();
	
	/**
	 * This method will return an instance of the GroupDispatcherView
	 * @return Returns and instance of the GroupDispatcherView
	 */
	public static GroupDispatcherView getInstance(){
		return instance;
	}
	
	private JFrame frame;
	
	private JScrollPane container;
	
	private ElevatorPanelList elevators;
	
	private JTabbedPane tabbedPane;
	
	private JPanel elevatorHolder;
	private JPanel floorHolder;
	
	private GroupDispatcherGUIObserverThread networkHandler;
	
	private RepaintManager manager;
	
	private InetAddress address;
	private int port;
	
	/**
	 * The main constructor for the GroupDispatcherView. This constructor will create the main frame and the menu bars
	 */
	public GroupDispatcherView(){
		
		elevators = new ElevatorPanelList();
		manager = new RepaintManager();
		
		createMainFrame();
		frame.setJMenuBar(createMenu());
	}
	
	/**
	 * This method will force the GUI to redraw itself. Its only called when a event has been received
	 */
	public void refreshGUI(){
		//container.invalidate();
		frame.repaint();
		tabbedPane.repaint();
		container.repaint();
		elevatorHolder.repaint();
		floorHolder.repaint();
	}
	
	/**
	 * This method will terminate all network handlers and the logger thread
	 */
	public void terminate(){
		// Checks if group dispatcher network handler is initialized and the socket has been created
		if (networkHandler != null && networkHandler.getRemoteSocket() != null){
			
			networkHandler.sendTerminationMessage();
		}else{
			Globals.getLogger().error("Network handler was null");
		}
		
		shutdownNetwork();
	}
	
	public void shutdownNetwork(){
		if (networkHandler != null && networkHandler.getRemoteSocket() != null){
			// Shuts down the thread
			networkHandler.shutdownThread();
		}
		
		// Checks if group dispatcher network handler is initialized and waits for thread to finish
		if (networkHandler != null){
			try {
				networkHandler.join(1000);
			} catch (InterruptedException e) {
				Globals.getLogger().error("NetworkHandler was not able to join", e);
				e.printStackTrace();
			}
		}else{
			Globals.getLogger().error("Network handler was null");
		}
		
		// Terminates the PersonDispatcherView's network handler
		PersonDispatcherView.getInstance().terminate();
		
		// Shuts down the logger thread
		try {
			Globals.getLogger().closeLogger();
		} catch (IOException e) {
			e.printStackTrace();
		}
		StatusUpdateThreadPool.getInstance().terminate();
		PersonStatusUpdateThreadPool.getInstance().terminate();
	}
	
	/**
	 * This method will connect the network handler to the GroupDispatcher server
	 * @param remoteAddr The address of the server
	 * @param remotePort The port of the server
	 */
	public void connectToServer(InetAddress remoteAddr,	int remotePort){
		this.address = remoteAddr;
		this.port = remotePort;
		
		networkHandler = new GroupDispatcherGUIObserverThread(remoteAddr, remotePort);
		networkHandler.addListener(new GroupDispatcherGUIObserverListener());
		networkHandler.start();
	}
	
	/**
	 * This method will reconnect to the server
	 */
	public void reconnectToServer(){
		// Shuts down the current socket
		if (networkHandler != null && networkHandler.getRemoteSocket() != null){
			networkHandler.shutdownThread();
		}
		// Reconnect to the server
		if (networkHandler != null){
			//networkHandler.reconnectToServer();
			if (this.address != null){
				resetElevators();
				resetHallCalls();
				connectToServer(this.address, this.port);
			}
		}
	}
	
	/**
	 * This method will create a menu bar
	 * @return
	 */
	private JMenuBar createMenu(){
		// Creates the main menu bar
		JMenuBar bar = new JMenuBar();
		// Creates the network menu
		JMenu networkMenu = new JMenu("Network");
		
		bar.add(networkMenu);
		// Creates the reconnect menu item and a listener for reconnecting to the server
		JMenuItem reconnectItem = new JMenuItem("Reconnect");
		networkMenu.add(reconnectItem);
		reconnectItem.addActionListener(new GroupDispatcherViewNetworkMenuItemListener(tabbedPane));
		
		// Creates an elevator menu
		JMenu elevatorMenu = new JMenu("Elevators");
		
		bar.add(elevatorMenu);
		
		// Create a kill all elevator menu item and a listener for enabling that item if there
		// are more then one elevators
		JMenuItem killItem = new JMenuItem("Kill All Elevators");
		elevatorMenu.add(killItem);
		//elevatorMenu.addMenuListener(new GroupDispatcherViewElevatorMenuListener(killItem));
		
		killItem.addActionListener(new ElevatorKillListener());
		return bar;
	}
	
	/**
	 * This method create the main frame. The JFrame is composed of a Tabbed panel which
	 * holds the Group and Person DispatcherViews
	 */
	private void createMainFrame(){
		// Creates main JFrame
		frame = new JFrame("Group Dispatcher View");
		frame.setMinimumSize(new Dimension(850, 600));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);		
		frame.getContentPane().setLayout(new BorderLayout());
		// Addeds a window listener for when the window is closing
		frame.addWindowListener(new GroupDispatcherWindowListener(this));
		
		createGroupDispatcherFrame();		
		createTabbedPanel();
	}
	
	/**
	 * This method creates the GroupDispatcher side of the GUI
	 */
	private void createGroupDispatcherFrame(){
		container = new JScrollPane(createFullView());
		container.setLayout(new ScrollPaneLayout());
		container.setMinimumSize(new Dimension(800, 600));
		container.setVerticalScrollBar(new JScrollBar());
	}
	
	/**
	 * This method create the tabbed pane that holds the GD view and the PD view
	 */
	private void createTabbedPanel(){
		tabbedPane = new JTabbedPane();
		tabbedPane.addTab("Group Dispatcher View", container);
		tabbedPane.addTab("Person Dispatcher View", PersonDispatcherView.getInstance());
		
		frame.getContentPane().add(tabbedPane);
	}
	
	/**
	 * This method will create the panel thats holds both the elevator views and the floor views
	 * @return Returns the container holding the elevator and floor views
	 */
	private JPanel createFullView(){
		JPanel holder = new JPanel();
		BorderLayout b = new BorderLayout();
		b.setHgap(0);
		holder.setLayout(b);
		
		// Places both the floor and elevator panels in the holder
		holder.add(createFloorPanel(), BorderLayout.WEST);
		holder.add(createElevatorPanel(), BorderLayout.CENTER);
		
		return holder;
	}
	
	/**
	 * This method will create the elevator panel and register the appropriate listeners with
	 * the GroupDispatcherStateHandler
	 * @return Returns the elevator panel
	 */
	private JPanel createElevatorPanel(){
		elevatorHolder = new JPanel();
		BoxLayout layout = new BoxLayout(elevatorHolder, BoxLayout.X_AXIS);
		elevatorHolder.setLayout(layout);
		
		// Register the listener for updating state of elevators
		GroupDispatcherStateHandler.getInstance().addListener(new ElevatorUpdateListener());
		// Register the listener for initial population of elevator state
		GroupDispatcherStateHandler.getInstance().addListener(new ElevatorInitializeListener());
		
		return elevatorHolder;
	}
	
	/**
	 * This method will return the number of elevator currently in the system
	 * @return Return the number of elevators in the system
	 */
	public int getElevatorNumbers(){
		int numbers = 0;
		synchronized(elevators){
			numbers = elevators.size();
		}
		return numbers;
	}
	
	/**
	 * This method will clear all the elevators in the system
	 */
	public synchronized void resetElevators(){
		elevators.clear();
		elevatorHolder.removeAll();
		GroupDispatcherStateHandler.getInstance().resetElevators();
	}
	
	/**
	 * This method will clear all the hall calls in the system
	 */
	private synchronized void resetHallCalls(){
		floorHolder.removeAll();
		GroupDispatcherStateHandler.getInstance().resetHallCalls();
	}
	
	/**
	 * This method will create an elevator with the given ID
	 * @param id The ID of the elevator that is to be created
	 */
	public synchronized void createElevator(int id){
		ElevatorPanel p = new ElevatorPanel(id);
		elevators.add(p);
		manager.markCompletelyClean(elevatorHolder);
		// Add elevator to system
		elevatorHolder.add(p);
		// Add car to elevator
		p.addElevatorCar();
		manager.markCompletelyDirty(elevatorHolder);
		frame.invalidate();
	}
	
	/**
	 * This method will remove an elevator with the given ID
	 * @param id The ID of the elevator that is to be removed
	 */
	public synchronized JPanel removeElevator(int id){
		// Checks if elevator is in system
		JPanel removed = elevators.remove(id);
		if (removed == null){
			return null;
		}
		manager.markCompletelyClean(elevatorHolder);
		// Remove elevator from GUI
		elevatorHolder.remove(removed);
		manager.markCompletelyDirty(elevatorHolder);
		return removed;
	}
	
	/**
	 * This method will create the main panel that holds all the floor panels. 
	 * @return Returns the panel that holds all floor panels
	 */
	private JPanel createFloorPanel(){
		floorHolder = new JPanel();
		BoxLayout layout = new BoxLayout(floorHolder, BoxLayout.Y_AXIS);
		floorHolder.setLayout(layout);
		
		// Register the listener for initial population of floor state
		GroupDispatcherStateHandler.getInstance().addListener(new FloorInitializeListener());
		
		return floorHolder;
	}
	
	/**
	 * This method will create all the floors panels when given the max amount of floors. The first panel
	 * will always be the default panel that just says floor
	 * @param maxFloor The max amount of floors in system
	 */
	public synchronized void createFloor(int maxFloor){
		floorHolder.removeAll();
		
		// Creates a floor title panel
		floorHolder.add(new FloorTitlePanel());
		
		for(int i = maxFloor - 1 + Constant.DEFAULT_FLOOR_START; i >= Constant.DEFAULT_FLOOR_START; i--){
			floorHolder.add(new FloorPanel(i));
		}
		Globals.getLogger().log("Create Floor End: " + maxFloor, this.getClass().getName(), "createFloor");
		System.out.println("Create Floor End : " + maxFloor);
	}
	
	/**
	 * This method will return a copy of the network handler
	 * @return Returns the network handler
	 */
	public GroupDispatcherGUIObserverThread getNetworkHandler(){
		return this.networkHandler;
	}
}
